C から Rust のコードを呼び出す
from 項目34:FFI境界を通過するものを制御しよう
基本的には「Rust から C のコードを呼び出す」場合と同じように定義することで、呼び出すことが可能
関数には extern "C" を付ける
Rust のシンボルはデフォルトで マングリング されるので、関数定義にも #[no_mangle] を付ける
したがって、他のすべてのシンボルと衝突する可能性があるため、「C に対する公開するシンボル名には 接頭辞 を付けることを検討しよう」
データ構造定義には #[repr(C)] を付けて、メモリレイアウト を C 互換にする
warning.icon 特に注意すべきポイント
C から渡されるポインタは 生ポインタ
そのため、操作するには 参照 に変換する必要がある
code:rs
#no_mangle
pub extern "C" fn add_contents(p: *const FfiStruct) -> u32 {
// C から与えられた生ポインタを Rust の参照に変換
let s: &FfiStruct = unsafe { &*p };
s.integer + s.byte as u32;
}
しかし、上記の定義だと NULL を渡すことができてしまう
code:c
uint32_t result = add_contents(NULL);
このように、Rust の参照のもつ仮定(不変条件)と保証に従うかをチェックするのはプログラマの役割 である
code:rs
#no_mangle
pub extern "C" fn add_contents(p: *const FfiStruct) -> u32 {
// C から与えられた生ポインタを Rust の参照に変換
let s = match unsafe { p.as_ref() } {
Some(r) => r,
None => return 0,
};
s.integer + s.byte as u32;
}
Rust のコードでは メモリ安全性 により、無効になった スタック 上の変数への 参照 が返されることはない
項目8:参照型とポインタ型に慣れよう
しかし、生ポインタでは起こりうる
code:rs
impl FfiStruct {
pub fn new(v: u32) -> Self {
Self { byte: 0, integer: v }
}
}
#no_mangle
pub extern "C" fn new_struct(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
&mut s // 関数から抜けると無効になるスタックオブジェクトへの生ポインタを返す
}
そのため、生ポインタは スタック ではなく ヒープ のメモリを参照すべきである
しかし、単に Box を用いるだけでは実現できない
code:rs
#no_mangle
pub extern "C" fn new_struct(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
let mut b = Box::new(s); // FFiStruct をヒープに移動する
&mut *b
}
∵ 所有 している Box はスタック上に存在する ため、関数が終了するとヒープメモリも解放するため
生存期間とヒープ#678745d975d04f00003c0976
代わりに Box::into_raw を用いると、Box を消費してヒープメモリを管理する責任を引き受けることができる
After calling this function, the caller is responsible for the memory previously managed by the Box.
code:rs
#no_mangle
pub extern "C" fn new_struct_heap(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
let mut b = Box::new(s);
Box::into(b)
}
逆に Box::from_raw を用いると、ヒープメモリを管理する Box を生成できる
これにより、ヒープメモリを解放することが可能となる
code:rs
#no_mangle
pub extern "C" fn free_struct_raw(p: *mut FfiStruct) {
if p.is_null() {
return;
}
let _b = unsafe {
Box::from_raw(p)
};
}
warning.icon C が誤って同じポインタを複数回解放した( free_struct_raw)場合は 二重解放 となり、 未定義動作 を引き起こす
これは Rust では防げない
「panic! は 未定義動作 となるので、通過しないようにしよう」
C もアンワインドでスタックが巻き戻るのを想定してないし、Rust 自身もパニックが FFI 境界を超えて伝播すること想定していない
項目18:Don't panic#6796105475d04f00003109c2
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目